package com.atguigu.gmall.list.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.gmall.list.dao.GoodsDao;
import com.atguigu.gmall.list.service.ListServic;
import com.atguigu.gmall.model.list.*;
import com.atguigu.gmall.model.product.BaseCategoryView;
import com.atguigu.gmall.model.product.BaseTrademark;
import com.atguigu.gmall.model.product.SkuAttrValue;
import com.atguigu.gmall.model.product.SkuInfo;
import com.atguigu.gmall.produck.client.ProductFeignClient;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;

import java.util.stream.Collectors;

/**
 * @Description: TODO
 * @Author: Mr.Cao
 * @DateTime: 2021/10/13 19:14
 * BMW F750GS
 **/
@Service
public class ListServiceImpl implements ListServic {

    @Autowired
    private GoodsDao goodsDao;
    @Autowired
    private ProductFeignClient productFeignClient;
    @Autowired
    private RestHighLevelClient restHighLevelClient; //ES提供的高级别客户端  --- 推荐使用
    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;//线程池

    @Autowired
    private RedisTemplate redisTemplate;
    /**
     * Sku 商品上架 创建elasticsearch 索引
     * */
    @Override
    public void onSale(long skuId) {
        Goods goods = new Goods();
        CompletableFuture<SkuInfo> skuInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {
            //1.查询skuinfo 数据
            SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
            goods.setId(skuId);//设置Id 为sku商品id
            goods.setDefaultImg(skuInfo.getSkuDefaultImg());//默认图片
            goods.setPrice(skuInfo.getPrice().doubleValue());//价格
            goods.setTitle(skuInfo.getSkuName());
            return skuInfo;
        }, threadPoolExecutor);

        CompletableFuture<Void> createTimeCompletableFuture = CompletableFuture.runAsync(() -> {
            //2.添加索引的时间
            Calendar calendar = Calendar.getInstance();
            goods.setCreateTime(calendar.getTime());
        },threadPoolExecutor);

        CompletableFuture<Void> baseTrademarkCompletableFuture = skuInfoCompletableFuture.thenAcceptAsync((skuInfo) -> {
            //3.品牌 Id 名称 logo
            //先获取品牌信息
            BaseTrademark baseTrademark = productFeignClient.getBaseTrademark(skuInfo.getTmId());
            goods.setTmId(skuInfo.getTmId());
            goods.setTmName(baseTrademark.getTmName());
            goods.setTmLogoUrl(baseTrademark.getLogoUrl());
        }, threadPoolExecutor);
        CompletableFuture<Void> category123CompletableFuture = skuInfoCompletableFuture.thenAcceptAsync((skuInfo) -> {
            //4. 一二三级分类
            //先查询一二三级分类
            BaseCategoryView category123 = productFeignClient.getCategory123(skuInfo.getCategory3Id());
            goods.setCategory1Id(category123.getCategory1Id());
            goods.setCategory2Id(category123.getCategory2Id());
            goods.setCategory3Id(category123.getCategory2Id());
            goods.setCategory1Name(category123.getCategory1Name());
            goods.setCategory2Name(category123.getCategory2Name());
            goods.setCategory3Name(category123.getCategory3Name());
        }, threadPoolExecutor);


        CompletableFuture<Void> skuAttrValueCompletableFuture1 = CompletableFuture.runAsync(() -> {
            //5.平台属性集合
            List<SkuAttrValue> skuAttrValueList = productFeignClient.getSkuAttrValueAndAttrAndAttrValue(skuId);
            //使用steam流进行转换
            List<SearchAttr> searchAttrList = skuAttrValueList.stream().map(skuAttrValue -> {
                SearchAttr searchAttr = new SearchAttr();
                searchAttr.setAttrId(skuAttrValue.getBaseAttrInfo().getId());
                searchAttr.setAttrName(skuAttrValue.getBaseAttrInfo().getAttrName());
                searchAttr.setAttrValue(skuAttrValue.getBaseAttrValue().getValueName());
                return searchAttr;
            }).collect(Collectors.toList());
            goods.setAttrs(searchAttrList);
        }, threadPoolExecutor);
        //组合任务, 当上面的线程任务完成, 执行
        CompletableFuture.allOf(skuInfoCompletableFuture,
                createTimeCompletableFuture,
                baseTrademarkCompletableFuture,
                category123CompletableFuture,
                skuAttrValueCompletableFuture1
                ).join();
        goodsDao.save(goods);
    }
    /** 测试: 不使用多线程异步编排
     * Sku 商品上架 创建elasticsearch 索引
     * */
    @Override
    public void onSaleNoJuc(long skuId) {
        Goods goods = new Goods();
        //1.查询skuinfo 数据
        SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
        goods.setId(skuId);//设置Id 为sku商品id
        goods.setDefaultImg(skuInfo.getSkuDefaultImg());//默认图片
        goods.setPrice(skuInfo.getPrice().doubleValue());//价格
        goods.setTitle(skuInfo.getSkuName());
        //2.添加索引的时间
        Calendar calendar = Calendar.getInstance();
        goods.setCreateTime(calendar.getTime());
        //3.品牌 Id 名称 logo
        //先获取品牌信息
        BaseTrademark baseTrademark = productFeignClient.getBaseTrademark(skuInfo.getTmId());
        goods.setTmId(skuInfo.getTmId());
        goods.setTmName(baseTrademark.getTmName());
        goods.setTmLogoUrl(baseTrademark.getLogoUrl());
        //4. 一二三级分类
        //先查询一二三级分类
        BaseCategoryView category123 = productFeignClient.getCategory123(skuInfo.getCategory3Id());
        goods.setCategory1Id(category123.getCategory1Id());
        goods.setCategory2Id(category123.getCategory2Id());
        goods.setCategory3Id(category123.getCategory2Id());
        goods.setCategory1Name(category123.getCategory1Name());
        goods.setCategory2Name(category123.getCategory2Name());
        goods.setCategory3Name(category123.getCategory3Name());
        //5.平台属性集合
        List<SkuAttrValue> skuAttrValueList = productFeignClient.getSkuAttrValueAndAttrAndAttrValue(skuId);
        //使用steam流进行转换
        List<SearchAttr> searchAttrList = skuAttrValueList.stream().map(skuAttrValue -> {
            SearchAttr searchAttr = new SearchAttr();
            searchAttr.setAttrId(skuAttrValue.getBaseAttrInfo().getId());
            searchAttr.setAttrName(skuAttrValue.getBaseAttrInfo().getAttrName());
            searchAttr.setAttrValue(skuAttrValue.getBaseAttrValue().getValueName());
            return searchAttr;
        }).collect(Collectors.toList());
        goods.setAttrs(searchAttrList);
        goodsDao.save(goods);
    }
    /**
     * Sku 商品上架 删除elasticsearch 索引
     * */
    @Override
    public void cancelSale(long skuId) {
        goodsDao.deleteById(skuId);
    }
    /**
     * 浏览sku商品 增加热度值
     * */
    @Override
    public void incrHostScore(long skuId, long score) {
        //将热度值 追加 到缓存中 对应的skuId值    返回的是总分数
        Double totalScore = redisTemplate.opsForZSet().incrementScore("HostScore", skuId, score);

        if (totalScore%10==0){ //由于 es索引库增删改性能差,所以当热度值积累到一定的程度,在更新es
            //先查询索引库中的数据
            Goods goods = goodsDao.findById(skuId).get();
            //修改热度值
            goods.setHotScore(totalScore.longValue());
            //进行保存
            goodsDao.save(goods);
        }
    }
    /**
     * 开始搜索,分词搜索
     * */
    @Override
    public SearchResponseVo search(SearchParam searchParam) {
        //1:创建索引请求对象  searchRequest
        SearchRequest searchRequest = buildSearchRequest(searchParam);
        //2:执行搜索 得到SearchResponse对象
        SearchResponse searchResponse = null;
        try {
            searchResponse =
                    restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //3:对返回值SearchResponse 进行结果解析
        SearchResponseVo vo = parseSearchResponse(searchResponse);
        //封装总当前页数,和每页条数
        vo.setPageNo(searchParam.getPageNo());
        vo.setPageSize(searchParam.getPageSize());
        //总页数  = （总条数 + 每页数 - 1）/每页数
        //计算总页数,
        long totalPages = (vo.getTotal() + vo.getPageSize() - 1) / vo.getPageSize();
        vo.setTotalPages(totalPages);
        return vo;
    }
    //创建索引请求对象
    private SearchRequest buildSearchRequest(SearchParam searchParam) {
        //1.创建请求对象 SearchRequest 并设置索引库的名称
        SearchRequest searchRequest = new SearchRequest().indices("goods");
        //2.构建查询的请求体
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //3.构建组合对象
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        //1.获取关键词
        String keyword = searchParam.getKeyword();
        if (!StringUtils.isEmpty(keyword)) {
            //有   match :    1)先分词     我是一个好人  我   是   一个   好人
            //               2）相当于 mysql  where
            //                         name like ‘%我%’ OR
            //                         name like ‘%是%’ OR
            //                         name like ‘%一个%’ OR
            //                         name like ‘%好人%’
            // must  must_not  should
            // and     not        or
            boolQueryBuilder.must(
                    QueryBuilders.matchQuery("title", keyword).operator(Operator.AND));
        } else {
            boolQueryBuilder.must(QueryBuilders.matchAllQuery());//查询全部
        }

        //2：品牌    品牌ID:品牌的名称  过滤条件
        String trademark = searchParam.getTrademark();
        if (!StringUtils.isEmpty(trademark)) {
            // mysql  where name =  华为
            String[] t = StringUtils.split(trademark, ":");
            boolQueryBuilder.filter(QueryBuilders.termQuery("tmId", t[0]));
        }
        //3、三级分类 过滤条件
        Long category1Id = searchParam.getCategory1Id();
        if (null != category1Id) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("category1Id", category1Id));
        }
        Long category2Id = searchParam.getCategory2Id();
        if (null != category2Id) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("category2Id", category2Id));
        }
        Long category3Id = searchParam.getCategory3Id();
        if (null != category3Id) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("category3Id", category3Id));
        }
        //4: 平台属性   （难）  过滤条件
        //  http://url?props=平台ID:平台值:平台属性名
        String[] props = searchParam.getProps();
        if (null != props && props.length > 0) {
            //儿子的组合对象
            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
            for (String prop : props) {
                String[] p = prop.split(":");
                if (null != p && p.length == 3) {
                    BoolQueryBuilder subBoolQuery = QueryBuilders.boolQuery();
                    subBoolQuery.must(QueryBuilders.termQuery("attrs.attrId", p[0]));
                    //subBoolQuery.must(QueryBuilders.termQuery("attrs.attrName",p[2]));
                    subBoolQuery.must(QueryBuilders.termQuery("attrs.attrValue", p[1]));
                    boolQuery.must(QueryBuilders.nestedQuery("attrs", subBoolQuery, ScoreMode.None));
                }
            }
            boolQueryBuilder.filter(boolQuery);
        }
        searchSourceBuilder.query(boolQueryBuilder);
        //5:排序   1:desc 1:asc 2:desc 2:asc
        String order = searchParam.getOrder();
        String field = "hotScore";//默认综合  排序
        if (!StringUtils.isEmpty(order)) {
            String[] o = order.split(":");
            switch (o[0]) {
                case "1":
                    break;
                case "2":
                    field = "price";
                    break;
                case "3":
                    field = "createTime";
                    break;
            }
            searchSourceBuilder.sort(field, "asc".equalsIgnoreCase(o[1]) ? SortOrder.ASC : SortOrder.DESC);
        } else {
            //默认排序
            searchSourceBuilder.sort(field, SortOrder.DESC);
        }
        //6:分页   limit  开始行 , 每页数
        Integer pageNo = searchParam.getPageNo();
        Integer pageSize = searchParam.getPageSize();
        searchSourceBuilder.from((pageNo - 1) * pageSize);//
        searchSourceBuilder.size(pageSize);//
        //7:高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("title")
                .preTags("<font style='color:red'>")  //前缀
                .postTags("</font>");
        searchSourceBuilder.highlighter(highlightBuilder);
        //8:聚合
        //8.1 : 品牌聚合
        searchSourceBuilder.aggregation(
                AggregationBuilders.terms("aggTmId").field("tmId")
                        .subAggregation(AggregationBuilders.terms("aggTmName").field("tmName"))
                        .subAggregation(AggregationBuilders.terms("aggTmLogoUrl").field("tmLogoUrl")));
        //8.2  平台属性聚合
        searchSourceBuilder.aggregation(AggregationBuilders.nested("aggAttrs", "attrs")
                .subAggregation(AggregationBuilders.terms("aggAttrId").field("attrs.attrId")
                        .subAggregation(AggregationBuilders.terms("aggAttrValue").field("attrs.attrValue"))
                        .subAggregation(AggregationBuilders.terms("aggAttrName").field("attrs.attrName"))));

        System.out.println("DSL:" + searchSourceBuilder.toString());
        searchRequest.source(searchSourceBuilder);
        //设置索引库的名称
        //searchRequest.indices("goods"); 在创建该对象时已经指定索引库的名称
        //设置 type
        //searchRequest.types("info","hhss");
        return searchRequest;
    }
    //对返回值SearchResponse 进行结果解析
    private SearchResponseVo parseSearchResponse(SearchResponse searchResponse) {
        SearchResponseVo vo = new SearchResponseVo();
        //1:总条数
        SearchHits hits = searchResponse.getHits();
        long totalHits = hits.totalHits;
        System.out.println("总条数：" + totalHits);
        vo.setTotal(totalHits);
        //2:商品集合
        SearchHit[] hits1 = hits.getHits();
        List<Goods> goodsList = Arrays.stream(hits1).map(h -> {
            Goods goods = JSONObject.parseObject(h.getSourceAsString(), Goods.class);
            Map<String, HighlightField> highlightFields = h.getHighlightFields();
            if (null != highlightFields && highlightFields.size() > 0) {
                HighlightField title = highlightFields.get("title");
                String s = title.getFragments()[0].toString();
                goods.setTitle(s);
            }
            return goods;
        }).collect(Collectors.toList());
        vo.setGoodsList(goodsList);
        //3:品牌集合
        Aggregations aggregations = searchResponse.getAggregations();
        ParsedLongTerms aggTmId = aggregations.get("aggTmId");
        List<SearchResponseTmVo> trademarkList = aggTmId.getBuckets().stream().map(bucket -> {
            SearchResponseTmVo tmVo = new SearchResponseTmVo();
            //ID
            tmVo.setTmId(((Terms.Bucket) bucket).getKeyAsNumber().longValue());
            //品牌的名称
            ParsedStringTerms aggTmName = ((Terms.Bucket) bucket).getAggregations().get("aggTmName");
            tmVo.setTmName(aggTmName.getBuckets().get(0).getKeyAsString());
            //品牌的Logo
            ParsedStringTerms aggTmLogoUrl = ((Terms.Bucket) bucket).getAggregations().get("aggTmLogoUrl");
            tmVo.setTmLogoUrl(aggTmLogoUrl.getBuckets().get(0).getKeyAsString());
            return tmVo;
        }).collect(Collectors.toList());
        vo.setTrademarkList(trademarkList);
        //4:平台属性集合
        ParsedNested aggAttrs = aggregations.get("aggAttrs");
        ParsedLongTerms aggAttrId = aggAttrs.getAggregations().get("aggAttrId");
        List<SearchResponseAttrVo> attrsList =
                aggAttrId.getBuckets().stream().map(bucket -> {
                    SearchResponseAttrVo attrVo = new SearchResponseAttrVo();
                    //ID
                    attrVo.setAttrId(((Terms.Bucket) bucket).getKeyAsNumber().longValue());
                    //平台属性名称
                    ParsedStringTerms aggAttrName = ((Terms.Bucket) bucket).
                            getAggregations().get("aggAttrName");
                    attrVo.setAttrName(aggAttrName.getBuckets().get(0).getKeyAsString());
                    //平台属性值集合
                    ParsedStringTerms aggAttrValue = ((Terms.Bucket) bucket)
                            .getAggregations().get("aggAttrValue");
                    List<String> attrValueList = aggAttrValue.getBuckets().stream()
                            .map(Terms.Bucket::getKeyAsString).collect(Collectors.toList());
                    attrVo.setAttrValueList(attrValueList);
                    return attrVo;
                }).collect(Collectors.toList());
        vo.setAttrsList(attrsList);
        return vo;
    }
}
